import smtplib
from copy import deepcopy
from hashlib import md5
import time
import os
import jwt
import shutil
from io import BytesIO
from zipfile import ZipFile
from threading import Thread as BaseThread
from django.http import JsonResponse
from config.constant import (
    JWT_EXPIRE,
    DEFAULT_EMAIL,
    ERROR_MASSAGE, DEFAULT_TOKEN_KEY,
)
from email.header import Header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication


class RestfulResponse(JsonResponse):
    def __init__(self, status: int, data=None, **kwargs):
        """
        规范一下返回数据：{'data': xxx, 'status': xxx, 'msg': xxx}
        :param status: 状态码
        :param data: 返回的数据
        :param kwargs:
        """
        temp = {
            'status': status,
            'data': data if data else {},
            'msg': ERROR_MASSAGE.get(status, '未设置')
        }
        super().__init__(temp, **kwargs)


def get_md5(s):
    """
    得到字符串的md5值
    """
    prv = md5()
    prv.update(s.encode())
    return prv.hexdigest()


def make_token(dict_info: dict, expire=JWT_EXPIRE) -> str:
    """  # 记得目前是用 dict_info={'stu_id': xxx} 来储存信息！！！
    用 dict_info 来定制 token，比如：{'stu_id': xxx}
    :param dict_info: 定制 token 的字典信息
    :param expire: 定制 token 的过期时间(exp)，优先级还是 dict_info 高
    :return: JWT
    """
    now = time.time()
    key = DEFAULT_TOKEN_KEY
    payload = {'exp': int(now + expire)} | dict_info
    return jwt.encode(payload, key, algorithm='HS256')


class Thread:  # 将线程封装，更容易使用
    class Wrapper:
        def __init__(self, func):
            self.func = func
            self.res = None

        def __call__(self, *args, **kwargs):
            self.res = self.func(*args, **kwargs)

    def __init__(self, func, *args, **kwargs):
        self.wrapper = self.Wrapper(func)
        self._thread = BaseThread(target=self.wrapper, args=args, kwargs=kwargs)
        self.running = False

    @property
    def res(self):
        """
        线程的返回值
        :return:
        """
        return self.wrapper.res

    @property
    def done(self):
        """
        线程任务结束否
        :return:
        """
        return not self._thread.is_alive()

    def start(self):
        if not self.running:
            self._thread.start()
            self.running = True


class Mail:
    def __init__(self, text: str = None, mode="plain", encoding="utf-8",
                 Subject=None, From=None, To=None,
                 files: list = None, images: list = None, *args, **kwargs):
        """
        创建一个邮件对象，必填的只有 From To  `:)`，又因为在配置文件里面有默认邮箱，所以 From 也可以不写✍
        :param text: 文本内容
        :param mode: 发送模式，默认 plain
        :param encoding: 编码，默认 utf-8
        :param Subject: 邮件主题
        :param From: 邮件显示的发件人，与真正发送的邮箱无关！！！
        :param To: 收件人，注意，装饰用的，与收件人无关！！！
        :param files: 文件路径列表，因为直接 open('xxx', 'rb')
        :param images: 图片文件路径列表，所以一般绝对路径，除非对相对路径有绝对的把握
        :param args:
        :param kwargs:
        """
        widgets = [MIMEText(text, mode, encoding)]
        if files:
            if not Subject:
                Subject = ''
            for i in files:
                temp = MIMEApplication(open(i, 'rb').read())
                temp.add_header('Content-Disposition', 'attachment', filename=i.split(os.sep)[-1])  # 附件信息
                widgets.append(temp)
        if images:
            for i in files:
                temp = MIMEImage(open(i, 'rb').read(), _subtype='octet-stream')
                temp.add_header('Content-Disposition', 'attachment', filename=i.split(os.sep)[-1])  # 附件信息
                widgets.append(temp)
        mail = MIMEMultipart()
        for i in widgets:
            mail.attach(i)
        mail['Subject'] = Header(Subject, encoding)  # 邮件主题
        mail['From'] = Header(From, encoding)  # 发件人
        mail['To'] = Header(To, encoding)  # 收件人
        self._mail = mail

    def send_email(mail, user, password, server, port: int, To: list, From=None, *args, **kwargs):
        """
        将邮件对象发送，除了 From 可以不填，其他必填
        :param mail: 可以是构造的邮件对象，可以为Mail对象，意味着可以当对象方法，也可以传入邮件当类方法
        :param user: 邮箱账户
        :param password: 邮箱密码或授权码
        :param server: 邮箱 smtp 服务器地址
        :param port: 邮箱 smtp 服务器端口
        :param To: 发给谁？记得是列表！！！是群发！！！真正的收件人邮箱！！！
        :param mail: 构造的邮件对象
        :param From: 显示的发送者，与真正的发送者无关，除非你不填
        :param args:
        :param kwargs:
        :return: None
        """
        server = smtplib.SMTP_SSL(server, port)
        server.login(user, password)
        if not From:
            From = user
        mail = mail._mail if isinstance(mail, Mail) else mail
        server.sendmail(From, To, mail.as_string())
        server.quit()

    def default_send(mail, To: list, *args, **kwargs):
        """
        采用默认配置来发送邮件，同样可以当类方法，也可以当实例方法
        :param mail: 构造邮件对象或者Mail对象
        :param To: 发给谁？收件人列表
        :param args:
        :param kwargs:
        :return:
        """
        mail = mail._mail if isinstance(mail, Mail) else mail
        Mail.send_email(user=DEFAULT_EMAIL['user'],
                        password=DEFAULT_EMAIL['password'],
                        server=DEFAULT_EMAIL['smtp']['server'],
                        port=DEFAULT_EMAIL['smtp']['port'],
                        To=To,
                        mail=mail,
                        *args, **kwargs)

    @staticmethod
    def quickly_send(text: str = None, To: list = None, From: str = None, Subject=None,
                     files: list = None, images: list = None, *args, **kwargs):
        """  # 和 Mail.mail 无区别，除了 Mail.mail 配置了默认信息
        摈弃杂乱信息，直接快捷利用默认配置发信息
        :param text: 文本信息
        :param To: 收件人列表
        :param From: 发送者的名字，装饰用的
        :param Subject: 邮件主题
        :param files: 路径列表
        :param images: 路径列表
        :return:
        """
        mail = Mail(text=text, From=From, Subject=Subject, files=files, images=images)
        Mail.default_send(mail, To=To)

    @classmethod
    def mail(cls, msg: str = None, to: list = None, sender=DEFAULT_EMAIL['header']['From'],
             header: str = DEFAULT_EMAIL['header']['Subject'], files=None, images=None, *a, **b):
        """
        摈弃杂乱信息，直接快捷利用默认配置发信息
        :param msg: 文本信息
        :param to: 收件人列表
        :param sender: 发送者的名字，装饰用的
        :param header: 邮件主题
        :param files: 路径列表
        :param images: 路径列表
        :return:
        """
        mail = Mail(text=msg, From=sender, Subject=header, files=files, images=images)
        Mail.default_send(mail, To=to)


class FileManager:
    """
    一个管理 本地/在线 文件的管理器，只负责存取、打包、读取文件流
    """

    @staticmethod
    def save(path, byte):
        """
        将文件保存到 path
        :param path: 路径以及文件名
        :param byte: 二进制流
        :return: None
        """
        with open(path, 'wb') as fp:
            fp.write(byte)

    @staticmethod
    def read(path) -> bytes:
        """
        读取 path 文件的二进制且返回
        :param path: 路径以及文件名
        :return: 二进制
        """
        with open(path, 'rb') as fp:
            buffer = fp.read()
        return buffer

    @staticmethod
    def zip(paths: list | dict) -> BytesIO:
        """
        将文件或字节流打包   TODO  object of type 'GetObjectResult' has no len()   |||   ValueError: I/O operation on closed file.
        :param paths: list: 文件名列表，文件夹会被递归归档、dict: {name: bytes, ...}
        :return: 二进制
        """
        buffer = BytesIO()
        temp_zip = ZipFile(buffer, 'w')
        if isinstance(paths, list):
            for file_name in paths:
                temp_zip.write(file_name)
        else:
            for file_name, bytes_io in paths.items():
                temp_zip.writestr(file_name, bytes_io)
        temp_zip.close()
        return buffer

    @classmethod
    def remove(cls, path):
        """
        执行删除文件的操作
        :param path:
        :return:
        """
        shutil.rmtree(path)


# 适配阿里云OSS的文件处理器
from config.constant import ALIYUN_OSS


class AliyunFileManager(FileManager):
    import oss2

    auth = oss2.Auth(ALIYUN_OSS['AccessKey ID'], ALIYUN_OSS['AccessKey Secret'])

    @classmethod
    def save(cls, path, byte):
        bucket = cls.oss2.Bucket(cls.auth, ALIYUN_OSS['Endpoint'], ALIYUN_OSS['Bucket Name'])
        bucket.put_object('test.png', byte)

    @classmethod
    def read(cls, path) -> BytesIO:
        bucket = cls.oss2.Bucket(cls.auth, ALIYUN_OSS['Endpoint'], ALIYUN_OSS['Bucket Name'])
        return bucket.get_object(path)

    @classmethod
    def remove(cls, path):
        bucket = cls.oss2.Bucket(cls.auth, ALIYUN_OSS['Endpoint'], ALIYUN_OSS['Bucket Name'])
        return bucket.delete_object(path)
